/*
 * Copyright (c) 2025 droidvm
 *
 * This software is licensed under the MIT License.
 * See LICENSE file in the project root for full license information.
 */

#pragma region 头文件

#define NO_LIBC_HEADER

#include <stdbool.h>
#include <stddef.h>		// C90, size_t                不同CPU上，长度不同
#include <stdint.h>		// C99, uint8_t, uint64_t ... 不同CPU上，长度相同
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <libgen.h>
#include <unistd.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <link.h>
#include <netdb.h>
#include <resolv.h>
#include <errno.h>
#include <stdnoreturn.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>

#pragma endregion

#pragma region 日志相关

#define DEFAULT_LOG_LEVEL 1
/* 日志级别说明:
 * 0=> no_log	不显示日志
 * 1=>log_err	只显示错误级别的日志
 * 2=>log_wrn	显示到警告级别的日志
 * 3=>log_info	显示的日志比警告级别稍多一些，会把信息级别的日志也显示出来
 * 4=>log_dbg	显示的日志比信息级别还多一些，会把调试级别的日志也显示出来
 */

#define	LOGE	(log_level>=1)
#define	LOGW	(log_level>=2)
#define	LOGI	(log_level>=3)
#define	LOGD	(log_level>=4)

static int  log_level = DEFAULT_LOG_LEVEL;

#define printf _printf

#pragma endregion

#pragma region 常量定义

	#if !defined(ARCH_AMD64) && !defined(ARCH_AMD32) && !defined(ARCH_ARM64) && !defined(ARCH_ARM32)

		/* DEEPSEEK 搜索
		x86_64架构，不使用glibc，用内联汇编的方式帮写出以下C函数，函数名前面要带下划线: 
		exit write close  open openat  mmap MPROTECT PRCTL EXECVE

		"C语言怎么实现 设置栈寄存器并跳转到指定地址执行"
		*/

		#if defined(__x86_64__)
				#define ARCH_AMD64 1
				#define USE_REG_TO_TRANSFER_FIRST_N_PARAM(__n__)	__attribute__((regparm(__n__)))

				void _setup_stack_and_jumpto(void* new_stack, void* target_addr) {
					asm volatile (
						"movq  %0, %%rsp      \n"	// 设置栈指针 (RSP)
						"jmpq *%1             \n"	// 无条件跳转 (RIP = 目标地址)
						:
						: "r"(new_stack), "r"(target_addr) // 输入参数
						:								// 输出操作数：显式标记 rsp 被修改
						//: "+r"(__builtin_nop())		// 输出操作数：显式标记 rsp 被修改
					);
					
					// 确保编译器知道不会返回
					__builtin_unreachable();
				}

		#elif defined(__i386__)
				#define ARCH_AMD32 1
				#define USE_REG_TO_TRANSFER_FIRST_N_PARAM(__n__)	__attribute__((regparm(__n__)))

				void _setup_stack_and_jumpto(void* new_stack, void* target_addr) {
					#pragma GCC diagnostic push
					#pragma GCC diagnostic ignored "-Wdeprecated"

					__asm__ volatile (
						"movl %[stack], %%esp\n"  // 将栈顶地址加载到 ESP 寄存器
						"jmp *%[addr]"            // 直接跳转到目标地址
						:
						: [stack] "r" (new_stack), [addr] "r" (target_addr)
						: "esp", "memory"          // 通知编译器 ESP 和内存被修改
					);
					#pragma GCC diagnostic pop

					// 确保编译器知道不会返回
					__builtin_unreachable();
				}

		#elif defined(__aarch64__)
				#define ARCH_ARM64 1
				#define USE_REG_TO_TRANSFER_FIRST_N_PARAM(__n__)

				void _setup_stack_and_jumpto(void* new_stack, void* target_addr) {
					#pragma GCC diagnostic push
					#pragma GCC diagnostic ignored "-Wdeprecated"
					#pragma GCC diagnostic ignored "-Winline-asm"
					asm volatile(
						// 设置新栈指针（ARM64使用满递减栈）
						"mov  sp, %[stack]\n\t"  
						
						// 直接跳转到目标地址（不保存返回地址）
						"br   %[entry]\n\t"       
						
						// 输入操作数
						: 
						: [stack]  "r"(new_stack),
						[entry]  "r"(target_addr)
						
						// 破坏列表：声明修改的寄存器和内存
						: "sp", "memory"
					);
					#pragma GCC diagnostic pop

					// 确保编译器知道不会返回
					__builtin_unreachable();
				}

		#elif defined(__ARM_EABI__)
				#define ARCH_ARM32 1
				#define USE_REG_TO_TRANSFER_FIRST_N_PARAM(__n__)

				void _setup_stack_and_jumpto(void* new_stack, void* target_addr) {
					#pragma GCC diagnostic push
					#pragma GCC diagnostic ignored "-Wdeprecated"
					#pragma GCC diagnostic ignored "-Winline-asm"
					__asm__ volatile (
						"mov sp, %[stack]\n"  // 将栈顶地址加载到SP寄存器
						"bx %[addr]"          // 跳转到目标地址
						:
						: [stack] "r" (new_stack), [addr] "r" (target_addr)
						: "sp", "memory"      // 通知编译器SP和内存被修改
					);
					#pragma GCC diagnostic pop

					// 确保编译器知道不会返回
					__builtin_unreachable();
				}

		#elif defined(__arm__)
				#error "不支持的CPU架构, ARM32 架构仅支持 EABI"
		#else
				#error "不支持的CPU架构"
		#endif
	#endif

	#pragma region action 相关的定义

		typedef struct {
			size_t action;
		} ProotActionClose;

		typedef struct {
			size_t action;
			size_t filepath;
		} ProotActionOpen;

		typedef struct {
			size_t action;
			size_t addr;
			size_t length;
			size_t prot;
			size_t offset;
			size_t clear_length;
		} ProotActionMmap;

		typedef struct {
			size_t action;
			size_t pointer;
		} ProotActionMProtect;

		typedef struct {
			size_t action;
			size_t stack_pointer;
			size_t entry_point;
			size_t at_phdr;
			size_t at_phent;
			size_t at_phnum;
			size_t at_entry;
			size_t at_execfn;
		} ProotActionStart;

		/* 不要使用 enum, 因为 sizeof(enum) 不一定等于 sizeof(size_t)
		* 用了可能会导致结构体变量访问出错  */
		#define PROOT_ACTION_CLOSE				0
		#define PROOT_ACTION_OPEN				1
		#define PROOT_ACTION_MMAP_FILE			2
		#define PROOT_ACTION_MMAP_AMAP			3
		#define PROOT_ACTION_MPROTECT			4
		#define PROOT_ACTION_START_TRACED		5
		#define PROOT_ACTION_START				6

	#pragma endregion

#pragma endregion

#pragma region 辅助函数

bool startsWith(const char *str, const char *prefix) {
    // 检查参数是否有效
    if (str == NULL || prefix == NULL) {
        return false;
    }
    
    // 获取前缀长度
    size_t prefix_len = strlen(prefix);
    
    // 如果前缀比字符串长，肯定不匹配
    if (prefix_len > strlen(str)) {
        return false;
    }
    
    // 比较前prefix_len个字符
    return strncmp(str, prefix, prefix_len) == 0;
}

/* musl 中的 vfprintf 依赖gcc中的浮点计算函数, 
 * 所以禁用后单独实现了一个简化版的 vfprintf */
int _putnumber(int fd, long n, int base, char lead, int maxwidth) {
	#define  MAX_NUMBER_BYTES  64

	const unsigned char hex_tab[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

	unsigned long m = 0;
	char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
	int count = 0, i = 0;

	*--s = '\0';

	if (n < 0)
		m = -n;
	else
		m = n;

	do
	{
		*--s = hex_tab[m % base];
		count++;

		if(base == 2 && (count % 8 == 0)) {*--s = ' ';}
	}
	while ((m /= base) != 0);

	if( maxwidth && count < maxwidth)
	{
		for (i = maxwidth - count; i; i--)
		{
			*--s = lead;
			if(base == 2 && (i>1) && ( (++count) %8 == 0)) {*--s = ' ';}
		}
	}

	if (n < 0 && base == 10)
		*--s = '-';

	return write(fd, s, strlen(s));
}

int __wrap_vfprintf(int fd, const char *fmt, va_list ap) {
	typedef int				int_s; // 不同CPU上，长度不同
	typedef unsigned int	int_u; // 不同CPU上，长度不同

	int rlt = 0;
	char lead = ' ';
	int  maxwidth = 0;

	char tmpc, *str;

	for(; *fmt != '\0'; fmt++) {
		if (*fmt != '%')
		{
			write(fd, fmt, 1);
			rlt += 1;
			continue;
		}
		lead = ' ';
		maxwidth = 0;

		//format : %08d, %8d,%d,%u,%x,%f,%c,%s
		fmt++;
		if(*fmt == '0')
		{
			lead = '0';
			fmt++;
		}

		while(*fmt >= '0' && *fmt <= '9')
		{
			maxwidth *= 10;
			maxwidth += (*fmt - '0');
			fmt++;
		}

		switch (*fmt)
		{
		case 'd':
			rlt += _putnumber(fd, va_arg(ap, int_s), 10, lead, maxwidth);
			break;
		case 'o':
			rlt += _putnumber(fd, va_arg(ap, int_u),  8, lead, maxwidth);
			break;
		case 'u':
			rlt += _putnumber(fd, va_arg(ap, int_u), 10, lead, maxwidth);
			break;
		case 'x':
		case 'X':
			rlt += _putnumber(fd, va_arg(ap, int_u), 16, lead, maxwidth);
			break;
		case 'p':
		case 'P':
			lead = '0';
			maxwidth = sizeof(void*) * 2;
			rlt += _putnumber(fd, va_arg(ap, size_t), 16, lead, maxwidth);
			break;
		case 'l':
			fmt++;
			switch(*fmt) {
				case 'd':
					rlt += _putnumber(fd, va_arg(ap, int_s), 10, lead, maxwidth);
					break;
				case 'x':
				case 'X':
					rlt += _putnumber(fd, va_arg(ap, int_u), 16, lead, maxwidth);
					break;
				case 'p':
				case 'P':
					lead = '0';
					maxwidth = sizeof(void*) * 2;
					rlt += _putnumber(fd, va_arg(ap, size_t), 16, lead, maxwidth);
					break;
			}
			break;
		case 'b':
			rlt += _putnumber(fd, va_arg(ap, int_u),  2, lead, maxwidth);
			break;
		case 'c':
			tmpc = (char)va_arg(ap, int);  // va_arg 的 char 会被提升为 int
			write(fd, &tmpc, 1);
			rlt += 1;
			break;
		case 's':
			str = (char*)va_arg(ap, const char *);
			if (!str) str = "(null)";
			int len = strlen(str);
			write(fd, (const char *)str, len);
			rlt +=len;
			break;
		default:
			write(fd, fmt, 1);
			rlt += 1;
			break;
		}
	}
	return rlt;

}

// 自定义 __aeabi_uidiv 实现
unsigned int __wrap___aeabi_uidiv(unsigned int numerator, unsigned int denominator) {
    // 错误处理 - 可以改为断言或记录错误
    if (denominator == 0) {
        // 在实际系统中可能需要更复杂的错误处理
        // 这里简单返回0，但最好根据应用需求处理
        fprintf(stderr, "Warning: Division by zero in __aeabi_uidiv\n");
        return 0;
    }
    
    unsigned int result = 0;
    
    // 使用二进制长除法算法
    for (int i = 31; i >= 0; i--) {
        if ((numerator >> i) >= denominator) {
            result |= (1U << i);
            numerator -= (denominator << i);
        }
    }
    
    return result;
}

int _printf(const char *fmt, ...) {
	// return 0;
	
	int rlt = -1;

	const char *tag = "[loader] ";
	write(2, tag, strlen(tag));

	va_list ap;
	va_start(ap, fmt);
	rlt = __wrap_vfprintf(2, fmt, ap);
	va_end(ap);

	return rlt;
}

#define exit_on(e, failmsg)	\
	(						\
		(e)					\
		?					\
			printf("%s:%d %s()\n", __FILE__, __LINE__, __FUNCTION__),	\
			printf("%s => %s\n\n\n", failmsg, #e),						\
			exit(-2)													\
		:					\
			(void)0			\
	)


#pragma endregion

#pragma region 核心功能

static size_t dump_argv(size_t argc, char **argv) {
	if(!LOGD) return argc;

	printf("argc: %d\n", (int)argc);
	while(argv && *argv) {
		printf("argv: %s\n", (char*)(*argv));
		argv++;
	}

	return argc;
}

static size_t dump_envp(char **envp) {

	size_t n_env = 0;
	char *logstr=NULL;

	#define LOG_TAG_STRING	"PROOT_LOADER_VERBOSE="
	while(envp && *envp) {
		n_env ++ ;
		// if(LOGD) printf("envp: %s\n", (char*)(*envp));

		if(startsWith((char*)(*envp), LOG_TAG_STRING)) {
			logstr = (char*)(*envp);
		}
		envp++;
	}

	if(logstr) {
		int tag_length = sizeof(LOG_TAG_STRING)-1;
		if(tag_length < 0) return n_env;

		char c = logstr[tag_length];
		if(c >= '0' && c <= '9') {
			log_level = c - '0';
		}
	}
	#undef LOG_TAG_STRING

	return n_env;
}

static void start_target_app(ProotActionStart *action, size_t at_base) {
	size_t *stack_pointer = (size_t *) action->stack_pointer;
	const size_t argc = stack_pointer[0];
	const size_t at_execfn = stack_pointer[1];
	char* name;

	/* stack_pointer 中的数据如下：
		*
		*   +------+--------+--------+--------+
		*   | argc | argv[] | envp[] | auxv[] |
		*   +------+--------+--------+--------+
		*/
	
	// stack_pointer 移到 argv 处
	stack_pointer += 1;

	dump_argv(argc, (char**)stack_pointer);

	// stack_pointer 移到 envp 处
	stack_pointer += argc + 1;

	size_t n_env = dump_envp((char**)stack_pointer);

	// stack_pointer 移到 auxv 处
	stack_pointer += n_env + 1;

	// 动态载入器，要处理辅助信息数组 auxv[]
	do {
		switch (stack_pointer[0]) {
		case AT_PHDR:
			stack_pointer[1] = action->at_phdr;
			break;

		case AT_PHENT:
			stack_pointer[1] = action->at_phent;
			break;

		case AT_PHNUM:
			stack_pointer[1] = action->at_phnum;
			break;

		case AT_ENTRY:
			stack_pointer[1] = action->at_entry;
			break;

		case AT_BASE:
			stack_pointer[1] = at_base;
			break;

		case AT_EXECFN:
			stack_pointer[1] = at_execfn;
			break;

		default:
			break;
		}
		stack_pointer += 2;
	} while (stack_pointer[0] != AT_NULL);

	name = basename((char*)(action->at_execfn));
	if(LOGD) printf("    进程名称修改为: %s\n", name);
	prctl(PR_SET_NAME, name, 0, 0, 0);

	if(LOGD) printf("正在跳转到目标程序: %s\n", name);
	_setup_stack_and_jumpto( ((void*)action->stack_pointer), ((void*)action->entry_point) );
}

/* proot_actions 是调试器传过来的(即proot进程传过来的)
 * 里面存着启动动作，如：
 * =================================================================================
 * 1). 文件打开、读取、映射、关闭、
 * 2). 数据区转代码区
 * 3). 目标程序的代码地址、栈地址、启动参数、环境变量
 */

/* proot 启动目标程序的流程：
 * =================================================================================
 * 一 fork 及 建立调试关系
 *    1). fork 分化出新的子进程
 *    2). 新的子进程会立马请求父进程调试自己，然后进入暂停状态
 *    3). 父进程则会在 子进程请求调试 后收到 "新增调试目标进程" 的消息(trap)，
 *        此时两进程间的调试关系建立完成, 子进程的后续所有的系统调用都会被父进程拦截处理!
 *    4). 收到 trace-trap 后，父进程创建、填写 proot_actions 清单, 并传递给子进程
 *        然后通过调试指令让子进程继续运行
 * 二 启动 loader => 加载动态库 => 跳转到目标程序运行
 *    5). 子进程通过 execvp(tracee->exe, argv[0] != NULL ? argv : default_argv);
 *        来运行目标程序，但由于子进程正处于被调试状态，
 *        所以 execvp 这个系统调用可以被父进程拦截，且父进程确实会拦截，
 *        还会将 "execvp 的目标程序" 替换为 loader，所以 execvp 其实是在启动 loader
 *    6). loader 是个静态链接的、不使用其它库的 elf-loader，
 *        它启动后会按照 proot_actions 清单中的指示，
 *        先载入目标程序所依赖的动态库,
 *        然后载入目标程序并设置好栈，
 *        最后跳转到目标程序的代码区，
 *        这样就完成了目标程序的启动任务，所以它叫loader
 */

static void process_proot_loader_action(void *proot_actions) {
	size_t at_base = 0;
	size_t fd = -1;
	size_t status;
	size_t cmd;

	LABEL_READ_ACTON:
	cmd = *((size_t*)proot_actions);
	switch (cmd) {
		case PROOT_ACTION_OPEN: LABEL_OPEN: {
			ProotActionOpen *action = (ProotActionOpen*)proot_actions;

			fd = open((char*)action->filepath, O_RDONLY, 0);
			if(LOGD) printf("open: %s\tfd => %d\n", (char*)action->filepath, fd);
			exit_on((int) fd < 0, "fail to PROOT_ACTION_OPEN");

			at_base = 0;

			proot_actions += sizeof(ProotActionOpen);
			break;
		}
		case PROOT_ACTION_MMAP_FILE: {
			ProotActionMmap *action = (ProotActionMmap*)proot_actions;

			status =(size_t) mmap((void*)action->addr, action->length, action->prot, MAP_PRIVATE | MAP_FIXED, fd, (unsigned long)(action->offset));

			if (!at_base) {
				if(LOGD) printf("rstb: 0x%p\t(at_base设置成这个地址了)\n", (void*)action->addr);
				at_base = action->addr;
			}

			if(LOGD) printf("mmap: 0x%p\t0x%x\t返回值: 0x%p, 偏移量: 0x%p\n", (void*)action->addr, (int)action->length, (void*)status, (void*)action->offset);
			exit_on(status != action->addr, "fail to PROOT_ACTION_MMAP_FILE");

			if (action->clear_length != 0) {
				size_t ptr_at = action->addr + action->length - action->clear_length;
				size_t ptr_to = action->addr + action->length;
				memset((void*)ptr_at, 0, ptr_to - ptr_at);

			}

			proot_actions += sizeof(ProotActionMmap);
			break;
		}
		case PROOT_ACTION_MMAP_AMAP: {
			ProotActionMmap *action = (ProotActionMmap*)proot_actions;

			status = (size_t)mmap((void*)action->addr, action->length, action->prot, MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);

			if(LOGD) printf("mmap: 0x%p\t0x%x\t返回值: 0x%p, 偏移量: 0x%p\n", (void*)action->addr, (int)action->length, (void*)status, (void*)action->offset);
			exit_on(status != action->addr, "fail to PROOT_ACTION_MMAP_AMAP");

			proot_actions += sizeof(ProotActionMmap);
			break;
		}
		case PROOT_ACTION_CLOSE: {
			if(LOGD) printf("clse: %d\n", (int)fd);
			status = close(fd);
			exit_on((int) status < 0, "fail to PROOT_ACTION_CLOSE");
			goto LABEL_OPEN;
		}
		case PROOT_ACTION_MPROTECT: {
			// 将指定内存区标记为可执行的指令流
			ProotActionMProtect *action = (ProotActionMProtect*)proot_actions;
			mprotect((void*)action->pointer, 1, PROT_READ | PROT_WRITE | PROT_EXEC | PROT_GROWSDOWN);

			proot_actions += sizeof(ProotActionMProtect);
			break;
		}
		case PROOT_ACTION_START_TRACED: {
			if(LOGD) printf("trac: 调试关系已建立\n");

			// 启用下面两行，则虚拟系统中将无法使用 gdb 调试软件
			// printf("异常! 正常是看不到这条消息的, 因为正常情况下跑不到这里, loader 加载之前，进程就已经处于被调试的状态了\n");
			// exit(-1);
		}
		case PROOT_ACTION_START: {
			ProotActionStart *action = (ProotActionStart*)proot_actions;
			if(LOGD) printf("exec: %s\n", (char*)(action->at_execfn));
			status = close(fd);
			exit_on((int) status < 0, "fail to PROOT_ACTION_START");
			start_target_app(action, at_base);

			printf("错误! 正常是看不到这条消息的, 因为正常情况下跑不到这里, 目标程序运行结束后都会调用 exit() 退出进程了\n");
			exit(0);
		}
		default: {
			printf("错误 => 不可识别的加载项: %d\n", (int)cmd);
			exit(-1);
		}
	}
	goto LABEL_READ_ACTON;

}

#pragma endregion

#pragma region 入口函数, 函数调用顺序: loader() => musl-libc._start => main()

/* 特别说明：
 * 1). 不能在loader() 中调用 printf !
 * 2). 也尽量不要在 loader() 中加其它代码，因为进程的运行环境还未准备就绪 */


void *ptr_from_proot = NULL;

void 
	USE_REG_TO_TRANSFER_FIRST_N_PARAM(1) 
loader(void *proot_actions) {
	ptr_from_proot = proot_actions;

	#if defined(ARCH_AMD32)
		/* 参考：
			* https://gitee.com/mirrors/musl/blob/master/arch/i386/crt_arch.h	_start   定义处, 纯32位模式
			* https://gitee.com/mirrors/musl/blob/master/arch/x32/crt_arch.h	_start   定义处, 64位CPU中的32位模式
			* https://gitee.com/mirrors/musl/blob/master/crt/crt1.c			_start_c 定义处, C代码，跨CPU兼容
			*/
		// 
		__asm__(
			"jmp _start \n"
		);
	#else
		#define start_code_select  0

		#if   start_code_select == 0
			extern int	_start();
						_start();
		#elif start_code_select == 1
			__asm__(
				"jmp _start \n"
			);
		#elif start_code_select == 2
			static char* fake_argv[2] = {"loader", NULL};
			static char* fake_envp[2] = {NULL};
			extern int	_start(int, char**, char**);
						_start(1, fake_argv, fake_envp);
		#endif
	#endif

}

// 避免报错：relocation R_X86_64_PC32 out of range;  references '_DYNAMIC'
void _DYNAMIC() {
}

int main(int argc, char** argv, char** envp) {
	#if defined(ARCH_ARM64)
		if(argc > 1) {
			printf("// argc: %d, argv[0]: %s\n", argc, argv[0]);
			void printf_offset_to_pokedata_workaround_for_proot_compile();
			printf_offset_to_pokedata_workaround_for_proot_compile();
			exit(0);
		}
	#endif

	if(LOGD) {
		printf("loader-%s.main() running...\n", (char*)_TARGET);
		printf("sizeof(size_t): %d, sizeof(unsigned long): %d\n", sizeof(size_t), sizeof(unsigned long));
		printf("loader entry: 0x%p, ptr_from_proot: 0x%p\n", loader, ptr_from_proot);
		printf("argc: %d, argv[0]: %s\n", argc, argv[0]);
		// printf("HOME: %s\n", getenv("HOME"));
	}
	if(!ptr_from_proot) {
		printf("未收到proot传过来的数据...\n\n");
		printf("这个程序是给proot调用的, 直接启动运行没有意义...\n");
		printf("源码地址: https://gitee.com/droidvm/ndkprootloader\n");
		exit(-1);
	}
	// detect_log_level_from_envp (ptr_from_proot);
	process_proot_loader_action(ptr_from_proot);
	exit(88);
}

#if defined(ARCH_ARM64)
	// https://stackoverflow.com/a/16087057
	// __attribute__((naked, used)) void pokedata_workaround() {
	__asm__ (
	".text\n"
	".global pokedata_workaround \n"
	"pokedata_workaround: \n"
	"	str x1, [x2] \n"
	"	.word 0xf7f0a000 \n"
	""
	);
	// }

	void printf_offset_to_pokedata_workaround_for_proot_compile() {
		extern void pokedata_workaround();
		printf("//loader-info.c (编译proot时用到)\n");
		printf("//这个文件是由 arm64-loader生成的\n");
		printf("#include <unistd.h>\n");
		printf("#define _ptr_pokedata_workaround              0x%p; // ptr1\n",      (void*)pokedata_workaround);
		printf("#define _ptr_loader                           0x%p; // ptr2\n",      (void*)loader);
		printf("const ssize_t offset_to_pokedata_workaround = 0x%p; // ptr2-ptr1\n", (void*)pokedata_workaround - (void*)loader);
	}
#else
	void printf_offset_to_pokedata_workaround_for_proot_compile() {}
#endif

#pragma endregion


